/*----------------------------------------------------------------------------
 *
 *  charpad.c
 *  LPAD and RPAD SQL functions for PostgreSQL.
 *
 *----------------------------------------------------------------------------
 */

#include "postgres.h"
#include "utils/builtins.h"
#include "utils/formatting.h"
#include "mb/pg_wchar.h"
#include "fmgr.h"

#include "plugin_postgres.h"
#include "plugin_orafce/builtins.h"

/* flags */
#define ON true
#define OFF false

/* Upper limit on total width of the padded output of *pad functions */
#define PAD_MAX 4000

PG_FUNCTION_INFO_V1_PUBLIC(orafce_lpad);
PG_FUNCTION_INFO_V1_PUBLIC(orafce_rpad);

/*
 * orafce_lpad(string text, length int32 [, fill text])
 *
 * Fill up the string to length 'length' by prepending
 * the characters fill (a half-width space by default)
 */
Datum orafce_lpad(PG_FUNCTION_ARGS)
{
    text *string1 = PG_GETARG_TEXT_PP(0);
    int32 output_width = PG_GETARG_INT32(1);
    text *string2 = PG_GETARG_TEXT_PP(2);
    text *ret = NULL;
    errno_t sret;
    char *ptr1 = NULL, *ptr2 = NULL, *ptr2start = NULL, *ptr2end = NULL, *ptr_ret = NULL, *spc = " ";
    int mlen, dsplen, s1blen, s2blen, hslen, total_blen = 0, s1_width = 0, s1_add_blen = 0, s2_add_blen = 0;
    bool s2_operate = ON, half_space = OFF, init_ptr = ON;

    /* validate output width (the 2nd argument) */
    if (output_width < 0)
        output_width = 0;
    if (output_width > PAD_MAX)
        output_width = PAD_MAX;

    /* get byte-length of the 1st and 3rd argument strings */
    s1blen = VARSIZE_ANY_EXHDR(string1);
    s2blen = VARSIZE_ANY_EXHDR(string2);

    /* validate the lengths */
    if (s1blen < 0)
        s1blen = 0;
    if (s2blen < 0)
        s2blen = 0;

    /* if the filler length is zero disable filling */
    if (s2blen == 0) {
        s2_operate = OFF; /* turn off string2 processing flag */
        output_width = 0; /* same behavior as Orafce database */
    }

    /* byte-length of half-width space */
    hslen = pg_mblen(spc);

    /*
     * Calculate the length of the portion of string1 to include in
     * the final output
     */
    ptr1 = VARDATA_ANY(string1);
    while (s1blen > 0) {
        /* byte-length and display length per character of string1 */
        mlen = pg_mblen(ptr1);
        dsplen = pg_dsplen(ptr1);

        /* accumulate display length of string1 */
        s1_width += dsplen;

        /*
         * if string1 is longer/wider than the requested output_width,
         * discard this character and prepend a half-width space instead
         */
        if (s1_width >= output_width) {
            if (s1_width != output_width) {
                /* secure bytes for a half-width space in the final output */
                if (output_width != 0) {
                    s1_add_blen += hslen;
                    half_space = ON;
                }
            } else /* exactly fits, so include this chracter */
            {
                s1_add_blen += mlen;
            }

            /*
             * turn off string2 processing because string1 already
             * consumed output_width
             */
            s2_operate = OFF;

            /* done with string1 */
            break;
        }

        /* accumulate string1's portion of byte-length of the output */
        s1_add_blen += mlen;

        /* advance one character within string1 */
        ptr1 += mlen;

        /* loop counter */
        s1blen -= mlen;
    }

    /* Calculate the length of the portion composed of string2 to use for padding */
    if (s2_operate) {
        int s2_add_width = 0;
        /* remaining part of output_width is composed of string2 */
        s2_add_width = output_width - s1_width;

        ptr2 = ptr2start = VARDATA_ANY(string2);
        ptr2end = ptr2 + s2blen;

        while (s2_add_width > 0) {
            /*  byte-length and display length per character of string2 */
            mlen = pg_mblen(ptr2);
            dsplen = pg_dsplen(ptr2);

            /*
             * output_width can not fit this character of string2, so discard it and
             * prepend a half-width space instead
             */
            if (dsplen > s2_add_width) {
                s2_add_blen += hslen;
                half_space = ON;

                /* done with string2 */
                break;
            }

            /* accumulate string2's portion of byte-length of the output */
            s2_add_blen += mlen;

            /* loop counter */
            s2_add_width -= dsplen;

            /* advance one character within string2 */
            ptr2 += mlen;

            /* when get to the end of string2, reset ptr2 to the start */
            if (ptr2 == ptr2end)
                ptr2 = ptr2start;
        }
    }

    /* allocate enough space to contain output_width worth of characters */
    total_blen = s1_add_blen + s2_add_blen;
    ret = (text *)palloc(VARHDRSZ + total_blen);
    ptr_ret = VARDATA(ret);

    /*
     * add a half-width space as a padding necessary to satisfy the required
     * output_width
     *
     * (memory already allocated as reserved by either s1_add_blen
     *  or s2_add_blen)
     */
    if (half_space) {
        sret = memcpy_s(ptr_ret, hslen, spc, hslen);
        securec_check(sret, "", "");
        ptr_ret += hslen;
    }

    /* prepend string2 padding */
    while (s2_add_blen > 0) {
        /* reset ptr2 to the string2 start */
        if (init_ptr) {
            init_ptr = OFF;
            ptr2 = ptr2start;
        }

        mlen = pg_mblen(ptr2);
        if (s2_add_blen < mlen)
            break;

        sret = memcpy_s(ptr_ret, mlen, ptr2, mlen);
        securec_check(sret, "", "");
        ptr_ret += mlen;
        ptr2 += mlen;

        /* loop counter */
        s2_add_blen -= mlen;

        /* when get to the end of string2, reset ptr2 back to the start */
        if (ptr2 == ptr2end)
            ptr2 = ptr2start;
    }

    init_ptr = ON;

    /* string1 */
    while (s1_add_blen > 0) {
        /* reset ptr1 back to the start of string1 */
        if (init_ptr) {
            init_ptr = OFF;
            ptr1 = VARDATA_ANY(string1);
        }

        mlen = pg_mblen(ptr1);

        if (s1_add_blen < mlen)
            break;

        sret = memcpy_s(ptr_ret, mlen, ptr1, mlen);
        securec_check(sret, "", "");
        ptr_ret += mlen;
        ptr1 += mlen;

        /* loop counter */
        s1_add_blen -= mlen;
    }

    SET_VARSIZE(ret, ptr_ret - (char *)ret);

    PG_RETURN_TEXT_P(ret);
}

/*
 * orafce_rpad(string text, length int32 [, fill text])
 *
 * Fill up the string to length 'length' by appending
 * the characters fill (a half-width space by default)
 */
Datum orafce_rpad(PG_FUNCTION_ARGS)
{
    text *string1 = PG_GETARG_TEXT_PP(0);
    int32 output_width = PG_GETARG_INT32(1);
    text *string2 = PG_GETARG_TEXT_PP(2);
    text *ret;
    char *ptr1, *ptr2 = NULL, *ptr2start = NULL, *ptr2end = NULL, *ptr_ret, *spc = " ";
    int mlen, dsplen, s1blen, s2blen, hslen, total_blen = 0, s1_width = 0, s1_add_blen = 0, s2_add_blen = 0;
    bool s2_operate = ON, half_space = OFF, init_ptr = ON;
    errno_t sret;

    /* validate output width (the 2nd argument) */
    if (output_width < 0)
        output_width = 0;
    if (output_width > PAD_MAX)
        output_width = PAD_MAX;

    /* get byte-length of the 1st and 3rd argument strings */
    s1blen = VARSIZE_ANY_EXHDR(string1);
    s2blen = VARSIZE_ANY_EXHDR(string2);

    /* validate the lengths */
    if (s1blen < 0)
        s1blen = 0;
    if (s2blen < 0)
        s2blen = 0;

    /* if the filler length is zero disable filling */
    if (s2blen == 0) {
        s2_operate = OFF; /* turn off string2 processing flag */
        output_width = 0; /* same behavior as Orafce database */
    }

    /* byte-length of half-width space */
    hslen = pg_mblen(spc);

    /*
     * Calculate the length of the portion of string1 to include in
     * the final output
     */
    ptr1 = VARDATA_ANY(string1);
    while (s1blen > 0) {
        /* byte-length and display length per character of string1 */
        mlen = pg_mblen(ptr1);
        dsplen = pg_dsplen(ptr1);

        /* accumulate display length of string1 */
        s1_width += dsplen;

        /*
         * if string1 is longer/wider than the requested output_width,
         * discard this character and prepend a half-width space instead
         */
        if (s1_width >= output_width) {
            if (s1_width != output_width) {
                /* secure bytes for a half-width space in the final output */
                if (output_width != 0) {
                    s1_add_blen += hslen;
                    half_space = ON;
                }
            } else /* exactly fits, so include this chracter */
            {
                s1_add_blen += mlen;
            }

            /*
             * turn off string2 processing because string1 already
             * consumed output_width
             */
            s2_operate = OFF;

            /* done with string1 */
            break;
        }

        /* accumulate string1's portion of byte-length of the output */
        s1_add_blen += mlen;

        /* advance one character within string1 */
        ptr1 += mlen;

        /* loop counter */
        s1blen -= mlen;
    }

    /* Calculate the length of the portion composed of string2 to use for padding */
    if (s2_operate) {
        int s2_add_width = 0;
        /* remaining part of output_width is composed of string2 */
        s2_add_width = output_width - s1_width;

        ptr2 = ptr2start = VARDATA_ANY(string2);
        ptr2end = ptr2 + s2blen;

        while (s2_add_width > 0) {
            /*  byte-length and display length per character of string2 */
            mlen = pg_mblen(ptr2);
            dsplen = pg_dsplen(ptr2);

            /*
             * output_width can not fit this character of string2, so discard it and
             * prepend a half-width space instead
             */
            if (dsplen > s2_add_width) {
                s2_add_blen += hslen;
                half_space = ON;

                /* done with string2 */
                break;
            }

            /* accumulate string2's portion of byte-length of the output */
            s2_add_blen += mlen;

            /* loop counter */
            s2_add_width -= dsplen;

            /* advance one character within string2 */
            ptr2 += mlen;

            /* when get to the end of string2, reset ptr2 to the start */
            if (ptr2 == ptr2end)
                ptr2 = ptr2start;
        }
    }

    /* allocate enough space to contain output_width worth of characters */
    total_blen = s1_add_blen + s2_add_blen;
    ret = (text *)palloc(VARHDRSZ + total_blen);
    ptr_ret = VARDATA(ret);

    /* string1 */
    while (s1_add_blen > 0) {
        /* reset ptr1 back to the start of string1 */
        if (init_ptr) {
            init_ptr = OFF;
            ptr1 = VARDATA_ANY(string1);
        }

        mlen = pg_mblen(ptr1);

        if (s1_add_blen < mlen)
            break;

        sret = memcpy_s(ptr_ret, mlen, ptr1, mlen);
        securec_check(sret, "", "");
        ptr_ret += mlen;
        ptr1 += mlen;

        /* loop counter */
        s1_add_blen -= mlen;
    }

    init_ptr = ON;

    /* append string2 padding */
    while (s2_add_blen > 0) {
        /* reset ptr2 to the string2 start */
        if (init_ptr) {
            init_ptr = OFF;
            ptr2 = ptr2start;
        }

        mlen = pg_mblen(ptr2);
        if (s2_add_blen < mlen)
            break;

        sret = memcpy_s(ptr_ret, mlen, ptr2, mlen);
        securec_check(sret, "", "");
        ptr_ret += mlen;
        ptr2 += mlen;

        /* loop counter */
        s2_add_blen -= mlen;

        /* when get to the end of string2, reset ptr2 back to the start */
        if (ptr2 == ptr2end)
            ptr2 = ptr2start;
    }

    /*
     * add a half-width space as a padding necessary to satisfy the required
     * output_width
     *
     * (memory already allocated as reserved by either s1_add_blen
     *  or s2_add_blen)
     */
    if (half_space) {
        sret = memcpy_s(ptr_ret, hslen, spc, hslen);
        securec_check(sret, "", "");
        ptr_ret += hslen;
    }

    SET_VARSIZE(ret, ptr_ret - (char *)ret);

    PG_RETURN_TEXT_P(ret);
}
